#include <cstdio>
#include <cstring>
#include <iostream>

#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>

#include "io_exceptions.h"
#include "posix_io.h"


void recurs_dir(std::string &path, std::vector<filenode> &nodes, long long int &size)
{
    DIR *dir = opendir(path.c_str());
    try {
        if (!dir) {
            throw io_exceptions::permission_denied_exception(path);
        }
    } catch (io_exceptions::permission_denied_exception e) {
        std::cerr << e.msg << std::endl;
        return;
    }

    struct stat stbuf;
    struct dirent *drnt;

    while ((drnt = readdir(dir)) != NULL) {
        if (!strcmp(drnt->d_name, ".") || !strcmp(drnt->d_name, "..")) {
            continue;
        }

        std::string npath = path;
        npath.append("/");
        npath.append(drnt->d_name);

#ifdef __linux__
        lstat(npath.c_str(), &stbuf);
#else
        stat(npath.c_str(), &stbuf);
#endif

        mode_t mode = stbuf.st_mode;

#ifdef __linux__
        if (S_ISLNK(mode) || S_ISCHR(mode) || S_ISBLK(mode) || S_ISFIFO(mode) || S_ISSOCK(mode)) {
            continue;
        }
#else
        if (S_ISCHR(mode) || S_ISBLK(mode)) {
            continue;
        }
#endif

        if (S_ISDIR(mode)) {
            recurs_dir(npath, nodes, size);
            continue;
        }


        try {
            if ((mode & S_IRUSR) != S_IRUSR) {
                throw io_exceptions::permission_denied_exception(npath);
            }

            int fd = open(npath.c_str(), O_RDONLY);
            if (fd < 0) {
                throw io_exceptions::permission_denied_exception(npath);
            } else {
                close(fd);
            }
        } catch (io_exceptions::permission_denied_exception e) {
            std::cerr << e.msg << std::endl;
            continue;
        }

        filenode fnode(npath, (long long int) stbuf.st_size);
        nodes.push_back(fnode);
        size += stbuf.st_size;
    }

    free(drnt);
    closedir(dir);
}


void calc_hash(std::string &path, unsigned char *data)
{
    int fd = open(path.c_str(), O_RDONLY);
    try {
        if (fd < 0) {
            throw io_exceptions::read_file_exception(path);
        }
    } catch (io_exceptions::read_file_exception e) {
        std::cerr << e.msg << std::endl;
        return;
    }

    SHA512_CTX sha512_content;
    SHA512_Init(&sha512_content);

    char buf[BLOCK_SIZE];

    while (true) {
        size_t nr = read(fd, buf, BLOCK_SIZE);
        if (nr <= 0) {
            break;
        }
        SHA512_Update(&sha512_content, buf, nr);
    }

    SHA512_Final(data, &sha512_content);
    close(fd);
}
